package com.caishi.lkx.exam.api;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.ListObjectsRequest;
import com.aliyun.oss.model.OSSObjectSummary;
import com.aliyun.oss.model.ObjectListing;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.caishi.lkx.common.AclResultCode;
import com.caishi.lkx.common.BaseApi;
import com.caishi.lkx.common.EncryptionApi;
import com.caishi.lkx.common.SystemService;
import com.caishi.lkx.common.config.DiamondConfig;
import com.caishi.lkx.common.ienum.DeviceType;
import com.caishi.lkx.common.role.Roles;
import com.caishi.lkx.exam.model.*;
import com.caishi.lkx.exam.mongo.KaoYanErrorQuestionModel;
import com.caishi.lkx.exam.records.*;
import com.caishi.lkx.exam.records.dto.*;
import com.caishi.lkx.exam.records.oss.RecordsOssConfig;
import com.caishi.lkx.exam.service.*;
import com.caishi.lkx.exam.service.impl.CoreQuestionCollectionService;
import com.caishi.lkx.exam.vo.*;
import com.caishi.lkx.user.service.IUserQuestionRecordsService;
import com.zzw.common.Wrappers;
import com.zzw.common.annotation.RateLimiterApi;
import com.zzw.common.annotation.RateLimiterGroup;
import com.zzw.common.annotation.ResultIgnore;
import com.zzw.common.entity.IBSEntity;
import com.zzw.common.entity.impl.BSDEntity;
import com.zzw.common.entity.impl.BSDUAbsEntity;
import com.zzw.common.entity.impl.BSEntity;
import com.zzw.common.entity.impl.BaseEntity;
import com.zzw.common.exception.BizException;
import com.zzw.common.exception.BizRuntimeException;
import com.zzw.common.exception.QPSFailException;
import com.zzw.common.file.OssPlugins;
import com.zzw.common.gateway.limit.RateLimiterApiTarget;
import com.zzw.common.qps.RateLimiterParams;
import com.zzw.common.qps.RejectStrategy;
import com.zzw.common.qps.spring.MemoryRateLimiterBean;
import com.zzw.common.qps.spring.RedisRateLimiterBean;
import com.zzw.common.service.TypeServiceRouter;
import com.zzw.common.service.model.NameModel;
import com.zzw.common.threadpool.SysThreadPool;
import com.zzw.common.utils.TimeUtil;
import com.zzw.common.utils.TimingUtil;

import com.caishi.lkx.exam.ExamResultCode;
import com.caishi.lkx.exam.QuestionCollection;
import com.caishi.lkx.exam.QuestionCollectionBlockItem;
import com.caishi.lkx.exam.dto.ApiQuestionRecordsDtoItem;
import com.caishi.lkx.exam.ienum.type.PaperRecordType;
import com.caishi.lkx.exam.ienum.type.QuestionCollectionType;
import com.caishi.lkx.exam.ienum.type.QuestionType;
import com.caishi.lkx.exam.ienum.type.RankType;

import com.caishi.lkx.exam.question.QuestionDataProcessComposite;
import com.caishi.lkx.exam.records.oss.OssFileService;
import com.caishi.lkx.exam.records.vo.CategoryCollectRecordsDtoItemVo;
import com.caishi.lkx.exam.records.vo.NewRecordsDtoItemVo;

import com.caishi.lkx.exam.util.QuestionUtil;
import com.caishi.lkx.user.model.MaterialLikeModel;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;


@Tag(name = "考试做题记录相关接口")
@Slf4j
@RestController
@RequestMapping("/exam/records")
public class RecordsApi extends BaseApi {

//    @Resource
//    private OssPlugins ossPlugins;
    @Resource
    @Qualifier("recordsOssPlugins")
    private OssPlugins recordsOssPlugins;
    @Resource
    private RecordsOssConfig recordsOssConfig;

    @Resource
    private RecordsService recordsService;

    @Resource
    private RecordsGetDataService recordsGetDataService;

    @Resource
    private RecordsSetDataService recordsSetDataService;

    @Resource
    private IQuestionService questionService;

    @Resource
    private IQuestionExpandService questionExpandService;

    @Resource
    private IExamQuestionRankService examQuestionRankService;

    @Resource
    private IUserCountRecordsService userCountRecordsService;

    @Resource
    private IPaperRecordsService paperRecordsService;

    @Resource
    private IPaperService paperService;

    @Resource
    private IChapterService chapterService;

    @Resource
    private RecordsDelService recordsDelService;

    @Resource
    private ICategoryService categoryService;

    @Resource
    private CoreQuestionCollectionService coreQuestionCollectionService;

    @Resource
    private IColumnService columnService;

    @Resource
    private IExColumnConfigReService columnConfigReService;

    @Resource
    private IEveryDayPracticeService everyDayPracticeService;

    @Resource
    protected RedisRateLimiterBean rateLimiter;

    @Resource
    protected MemoryRateLimiterBean memoryRateLimiterBean;

    @Resource
    private IWeekRankService weekRankService;

    @Resource
    private DiamondConfig diamondConfig;

    @Resource
    private SystemService systemService;

    @Resource
    private IMaterialLikeService materialLikeService;

    @Resource
    private INoteService noteService;

    @Resource
    private IQuestionTypeService questionTypeService;

    @Resource
    private OssFileService ossFileService;

    @Resource
    private MongoTemplate mongoTemplate;

    @Resource
    private IUserQuestionRecordsService userQuestionRecordsService;

    private static final QuestionDataProcessComposite questionDataProcessComposite = QuestionDataProcessComposite.instance;

    @PostMapping(path = "/question-collection", consumes = "application/json")
    @Operation(summary = "获取试题集合")
    @Roles
    @EncryptionApi
    @RateLimiterGroup(value = {
            @RateLimiterApi(namespace = "exam:safe-generate:", maxRate = 2, recoveryCount = 2, target = RateLimiterApiTarget.user, rejectStrategy = RejectStrategy.wait, bean = "memory"),
            @RateLimiterApi(namespace = "exam:safe-generate:", maxRate = 10, target = RateLimiterApiTarget.ip, rejectStrategy = RejectStrategy.wait, bean = "memory")

    })
    public QuestionCollectVo questionCollection(@RequestParam QuestionCollectionType collectionType,
                                                @RequestParam String industryCode,
                                                @RequestParam String examId,
                                                @RequestParam Long categoryId,
                                                String columnId,
                                                @RequestParam String id,
                                                @RequestParam(defaultValue = "true") Boolean setRightAnswer
                                                //String industryName,
                                                // String examName,
                                                //String categoryName
    ) throws BizException {
        TimingUtil.start("总时间");
        var userId = userContext.currentUserId();
        //var localGroupId = userContext.currentLocalGroupId();
        var ct = userContext.currentTokenData().getClientType();
        all:
        if (collectionType == QuestionCollectionType.chapter) {
            TimingUtil.start();
            if (StrUtil.isBlank(columnId)) {
                var s = paperRecordsService.getById(id);
                if (s != null && s.getType() == PaperRecordType.errorCombination && s.getUserId().equals(userId)) {
                    TimingUtil.stop("前置检查耗时");
                    break all;
                }
                throw new BizRuntimeException(AclResultCode.noPower);
            }
            boolean auth;
            try {
                auth = columnService.currentHaveAuth(null, null, userId, categoryId, columnId, ct);
            } catch (BizException e) {
                auth = false;
            }
            if (!auth) {
                //if (collectionType == QuestionCollectionType.chapter) {
                    var chapter = chapterService.getById(id);
                    String[] pidArray = chapter.getPath().split("/");
                    if (!columnConfigReService.configIsFree(columnId, chapter.getId())) {
                       for (String pid : pidArray){
                           if (columnConfigReService.configIsFree(columnId, pid)) {
                               TimingUtil.stop("前置检查耗时");
                               break all;
                           }
                       }
                    }else{
                        TimingUtil.stop("前置检查耗时");
                        break all;
                    }
//                } else {
//                    if (columnConfigReService.configIsFree(columnId, id)) {
//                        TimingUtil.stop("前置检查耗时");
//                        break all;
//                    }
//                }
                if (!userContext.currentTokenData().getDevice().equals(DeviceType.pc)) {
                    throw new BizRuntimeException(ExamResultCode.clientNotOpen);
                } else {
                    throw new BizRuntimeException(AclResultCode.noPower);
                }
            }
            TimingUtil.stop("前置检查耗时");
        } else if (collectionType == QuestionCollectionType.everyDayPractice) {
            var data = everyDayPracticeService.getById(id);
            if (!data.getUserId().equals(userId) && !data.getUserId().equals(userContext.getDuid())) {
                throw new BizRuntimeException(AclResultCode.noPower);
            }
        } else if (collectionType == QuestionCollectionType.paper) {
            var records = paperRecordsService.getById(id);
            if (!records.getUserId().equals(userId)) throw new BizRuntimeException(AclResultCode.noPower);
            boolean b1 = !columnService.currentHaveAuth(null, null, userId, categoryId, columnId, ct);
            boolean b2 = !records.getType().equals(PaperRecordType.errorCombination);
            boolean b3 = !(records.getType().equals(PaperRecordType.paperLibrary));
            boolean b4 = !columnConfigReService.configIsFree(columnId, records.getPaperId());
            if (b1 && b2 && b3 && b4) {

                if (!userContext.currentTokenData().getDevice().equals(DeviceType.pc)) {
                    throw new BizRuntimeException(ExamResultCode.clientNotOpen);
                } else {
                    throw new BizRuntimeException(AclResultCode.noPower);
                }
            }
        } else {
            // 高频错题
            if (!userContext.loginStatus()) throw new BizRuntimeException(AclResultCode.noPower);
            RecordsCollectType rt;
            if (collectionType == QuestionCollectionType.rankError) {
                rt = RecordsCollectType.rankError;
            } else if (collectionType == QuestionCollectionType.rankCollect) {
                rt = RecordsCollectType.rankCollect;
            } else if (collectionType == QuestionCollectionType.hotQue) {
                rt = RecordsCollectType.hotQue;
            } else {
                return null;
            }
            id = rt.staticCid(id);
            var s = recordsGetDataService.getQuestionRecordsResult(userId, industryCode, examId, categoryId, rt, id);
            var list = questionService.generateQuestionBlock(categoryId, null, null, s.getData().keySet().stream().toList(), null, null);
            var collectModel = new ChapterModel();
            collectModel.setContent(list);
            var result = coreQuestionCollectionService.clientCollectionData(collectModel, industryCode, examId, categoryId);
            result.setRecordsType(rt);
            result.setType(collectionType);
            result.setId(id);
            var time = id.replaceAll("\\D", "");
            var t = LocalDate.now();
            if (StrUtil.isNotBlank(time)) {
                t = LocalDate.parse(time, TimeUtil.DATE_TIME_FORMATTER_DAY_YMD);
            }
            result.setName(otherCollectName("", "", "", t, rt));
            var collectData = recordsGetDataService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, rt, id);
            coreQuestionCollectionService.safeProcess(new QuestionRecordsDto(), collectData, result, userId, userId, false);
            coreQuestionCollectionService.collectVoForEach(result, q -> {
                q.setCn(result.getName());
                q.setCid((String) result.getId());
            });
            return result;
        }
        TimingUtil.start();
        var service = ((QuestionCollectionService<?, Serializable>) TypeServiceRouter.router(collectionType));
        QuestionCollectVo questionCollectVo = service.clientCollectionData(userId, null, id, industryCode, examId, categoryId,setRightAnswer);
        TimingUtil.stop("试题部分");
        if (collectionType == QuestionCollectionType.paper) {
            for (var item : questionCollectVo.getContent()) {
                item.setDesc(null);
            }
        }
        TimingUtil.stop("总时间", true);
        return questionCollectVo;
    }

    @GetMapping("/category-collect-records")
    @Operation(summary = "获取科目下的集合做题记录")
    public IPage<CategoryCollectRecordsDtoItemVo> categoryCollectRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam(required = false) LocalDateTime startTime,
            @RequestParam(required = false) LocalDateTime endTime,
            @RequestParam(required = false) QuestionCollectionType collectionType,
            boolean app,
            @ModelAttribute Page<CategoryCollectRecordsDtoItemVo> page
    ) {
        String userId = userContext.currentUserId();
        Map<String, String> paperMap = new HashMap<>();
        Map<String, PaperRecordsModel> idMap = new HashMap<>();
        if (collectionType == QuestionCollectionType.paper) {
            var records = paperRecordsService.selectList(paperRecordsService.wrappers()
                    .select(BSDEntity::getId, PaperRecordsModel::getPaperId, PaperRecordsModel::getEndTime)
                    .eq(PaperRecordsModel::getUserId, userId)
                    .eq(PaperRecordsModel::getCategoryId, categoryId)
                    .eq(PaperRecordsModel::getType, PaperRecordType.paperLibrary)
            );
            paperMap.putAll(records.stream().collect(Collectors.toMap(PaperRecordsModel::getPaperId, PaperRecordsModel::getPaperId, (v, v1) -> v)));
            idMap.putAll(records.stream().collect(Collectors.toMap(PaperRecordsModel::getId, v -> v)));
        }
        var result = recordsGetDataService.ossMapDtoPage(
                recordsGetDataService.getCategoryCollectRecordsResult(userId, industryCode, examId, categoryId),
                data -> {
                    Stream<CategoryCollectRecordsDtoItemVo> categoryCollectRecordsDtoItemVoStream = data.keySet().stream()
                            .map(key -> {
                                var ks = key.split("_");
                                CategoryCollectRecordsDtoItemVo vo = data.getObject(key, CategoryCollectRecordsDtoItemVo.class);
                                vo.setType(RecordsCollectType.valueOf(ks[0]));
                                vo.setId(ks[1]);
                                return vo;
                            })
                            .sorted((v, v1) -> Long.compare(v1.getT(), v.getT()))
                            .filter(v -> {
                                var res = true;
                                if (startTime != null) {
                                    res = v.getT() >= LocalDateTimeUtil.toEpochMilli(startTime);
                                }
                                if (res && endTime != null) {
                                    res = v.getT() < LocalDateTimeUtil.toEpochMilli(endTime);
                                }
                                var ct = v.getType().type2CollectType();
                                if (res && collectionType == QuestionCollectionType.paper) {
                                    res = ct == collectionType;
                                } else {
                                    res = res && ct != QuestionCollectionType.paper;
                                }
                                if (res && app) {
                                    res = ct != QuestionCollectionType.everyDayPractice;
                                } else {
                                    res = res && v.getType() != RecordsCollectType.paperRecordChapterPractice;
                                }
                                return res;
                            });
                    if (collectionType == QuestionCollectionType.paper && !app) {
                        //去除重复的试卷id
                        Set<String> paperids = new HashSet<>();
                        return categoryCollectRecordsDtoItemVoStream.filter(f -> {
                            if (f.getType() != RecordsCollectType.paperRecordPaperLibrary) return true;
                            PaperRecordsModel paperRecordsModel = idMap.get(f.getId());
                            if (null == paperRecordsModel) return false;
                            if (paperids.contains(paperRecordsModel.getPaperId())) {
                                return false;
                            }
                            if (null != paperRecordsModel.getPaperId())
                                paperids.add(paperRecordsModel.getPaperId());
                            return true;
                        });
                    }
                    return categoryCollectRecordsDtoItemVoStream;
                }
                ,
                (int) page.getCurrent(),
                (int) page.getSize());
        Map<String, PaperRecordsModel> map = new HashMap<>();
        Map<String, PaperModel> paperModelMap = new HashMap<>();
        if (collectionType == QuestionCollectionType.paper) {
            var ids = result.getRecords().stream()
                    .filter(v -> {
                                if (v.getType() == RecordsCollectType.paperRecordPaperLibrary) {
                                    map.put(v.getId(), idMap.get(v.getId()));
                                }
                                return v.getType() == RecordsCollectType.paperRecordSpecialPractice ||
                                        v.getType() == RecordsCollectType.paperRecordRandomFormPaper ||
                                        v.getType() == RecordsCollectType.paperRecordChapterPractice ||
                                        v.getType() == RecordsCollectType.paperRecordErrorCombination;
                            }
                    ).map(CategoryCollectRecordsDtoItemVo::getId)
                    .toList();
            map.putAll(ids.isEmpty() ? new HashMap<>() : paperRecordsService.selectList(paperRecordsService.wrappers()
                    .select(BSDEntity::getId, PaperRecordsModel::getPaperId, PaperRecordsModel::getEndTime)
                    .in(BSDEntity::getId, ids)
            ).stream().collect(Collectors.toMap(PaperRecordsModel::getId, v -> v)));
            var paperIds = map.values().stream().filter(Objects::nonNull).map(PaperRecordsModel::getPaperId).distinct().toList();
            if (!paperIds.isEmpty()) {
                paperModelMap.putAll(paperService.selectList(paperService.wrappers()
                        .select(PaperModel::getPaperType, BaseEntity::getId)
                        .in(BaseEntity::getId, paperIds)
                ).stream().collect(Collectors.toMap(PaperModel::getId, v -> v)));
            }
        }
        result.getRecords().parallelStream().forEach(v -> {
            if (v.getType() == RecordsCollectType.paperRecordSpecialPractice ||
                    v.getType() == RecordsCollectType.paperRecordPaperLibrary ||
                    v.getType() == RecordsCollectType.paperRecordRandomFormPaper ||
                    v.getType() == RecordsCollectType.paperRecordChapterPractice ||
                    v.getType() == RecordsCollectType.paperRecordErrorCombination
            ) {
                var m = map.get(v.getId());
                if (m != null) {
                    v.setPaperHaveFinish(m.getHaveFinish());
                    if (null != m.getErrorCount()) {
                        v.setEc(m.getErrorCount());
                    }
                }
                if (m != null && (v.getType() == RecordsCollectType.paperRecordPaperLibrary || v.getType() == RecordsCollectType.paperRecordChapterPractice)) {
                    var m1 = paperModelMap.get(m.getPaperId());
                    if (m1 != null) {
                        v.setPaperType(m1.getPaperType());
                        v.setPaperId(m.getPaperId());
                    }
                    v.setDel(m1 == null);
                }
                v.setDel(m == null);
            }
            if (v.getType() == RecordsCollectType.rankCollect || v.getType() == RecordsCollectType.rankError || v.getType() == RecordsCollectType.hotQue) {
                var r = recordsGetDataService.getQuestionRecordsResult(userId, industryCode, examId, categoryId, v.getType(), v.getId());
                var tc = r.getData().size();
                v.setTc(tc);
                v.setDc(tc);
                v.setEc((int) r.getData().values().stream().filter(v1 -> v1 instanceof JSONObject vj && !vj.getBoolean("rt")).count());
            }
        });
        return result;

    }

    @Parameters(value = {
            @Parameter(name = "leaf", description = "是否是叶子节点 章节时需要"),
            @Parameter(name = "groupByCollect", description = "章节非叶子节点时让返回试题按照集合名聚集"),
    })
    @PostMapping("/error-collection")
    @Operation(summary = "获取错题试题集合")
    public QuestionCollectVo categoryErrorDataCollect(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            String collectId,
            @RequestParam QuestionCollectionType collectionType,
            @RequestParam(defaultValue = "true") Boolean leaf,
            @RequestParam(defaultValue = "false") Boolean groupByCollect,
            String industryName,
            String examName,
            String categoryName,
            boolean view
    ) {
        String userId = userContext.currentUserId();
        var result = getCollectData(userId, industryCode, examId, categoryId, collectionType, collectId, leaf, true, groupByCollect, view, industryName, examName, categoryName);
        result.setName("错题集合");
        return result;
    }

    @PostMapping("/collect-collection-new")
    @Operation(summary = "获取收藏试题集合")
    @SneakyThrows
    public QuestionCollectVo categoryCollectDataCollectNew(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            String collectId,
            @RequestParam QuestionCollectionType collectionType,
            @RequestParam RankType type

    )  {
        String userId = userContext.currentUserId();

        //判断试卷和章节获取详情
        QuestionCollection<?, ?>  model = null;
        if (collectionType == QuestionCollectionType.paper) {
            model = paperService.getById(collectId);
        } else {
            model = chapterService.getById(collectId);
        }
        //查找做题记录和收藏记录
        Future<QuestionRecordsDto> future = null;
        Future<CollectionErrorAndCollectRecordDataDto> collectFuture = null;
        if (StrUtil.isNotEmpty(userId)) {
            future = SysThreadPool.submit(() -> recordsGetDataService.getQuestionRecordsResult(userId, industryCode, examId, categoryId, collectionType == QuestionCollectionType.paper ? RecordsCollectType.paperRecordChapterPractice : RecordsCollectType.chapter, collectId));
            collectFuture = SysThreadPool.submit(() -> recordsGetDataService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, collectionType == QuestionCollectionType.paper ? RecordsCollectType.paperRecordChapterPractice : RecordsCollectType.chapter, collectId));
        }
        //获取错题或者收藏Ids
        ArrayList<String> questionIdList = null;
        if(type == RankType.collect){
            questionIdList =  new ArrayList<>(recordsGetDataService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, RecordsCollectType.collectType2Type(collectionType), collectId).getData().keySet());
        }  else{
            questionIdList = new ArrayList<>( recordsGetDataService.getErrorInCollectDataResult(userId, industryCode, examId, categoryId, RecordsCollectType.collectType2Type(collectionType), collectId).getData().keySet());
        }
        //获取试题集合
        var questionData = questionService.clientQuestionCollection(questionIdList, industryCode, examId, categoryId);
        //处理数据，装载试题数据
        var result =  coreQuestionCollectionService.clientCollectionData(model, questionData, industryCode, examId, categoryId);
        if (future != null) {
            coreQuestionCollectionService.safeProcess(future.get(), collectFuture.get(), result, userId, collectId, true);
        }
        return result;

    };

    @PostMapping("/collect-collection")
    @Operation(summary = "获取收藏试题集合")
    public QuestionCollectVo categoryCollectDataCollect(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            String collectId,
            @RequestParam QuestionCollectionType collectionType,
            @RequestParam(defaultValue = "true") Boolean leaf,
            @RequestParam(defaultValue = "false") Boolean groupByCollect,
            boolean view,
            String industryName,
            String examName,
            String categoryName
    ) {
        String userId = userContext.currentUserId();
        if (collectionType.equals(QuestionCollectionType.paper)) {
            //如果是试卷收藏
            MaterialLikeModel model = materialLikeService.selectFirst(Wrappers.<MaterialLikeModel>lambdaQuery()
                    .eq(MaterialLikeModel::getRelationId, collectId)
                    .eq(MaterialLikeModel::getCreatedBy, userId)
                    .eq(MaterialLikeModel::getPassive, 1));
            if (model != null) {
                return getPaperCollectInfo(model, userId, industryCode, examId, categoryId);
            }
        }
        var result = getCollectData(userId, industryCode, examId, categoryId, collectionType, collectId, leaf, false, groupByCollect, view, industryName, examName, categoryName);
        result.setName("收藏集合");
        return result;
    }


    @GetMapping("/category-records")
    @Operation(summary = "获取科目下做题数据")
    @Roles
    public CategoryRecordsVo categoryRecords(
            @RequestParam Long categoryId,
            @RequestParam String examId,
            @RequestParam String industryCode

    ) throws ExecutionException, InterruptedException {
        var result = new CategoryRecordsVo();
        String userId = userContext.currentUserId();
        if (StrUtil.isBlank(userId)) {
            result.setAllCount(categoryService.fetchCategoryQuestionCount(categoryId));
            return result;
        }
        var f = SysThreadPool.submit(() -> {
            result.setTodayCount(userCountRecordsService.userTodayData(userId).getTc());
            result.setAllCount(categoryService.fetchCategoryQuestionCount(categoryId));
        });
        var f1 = SysThreadPool.submit(() -> {
            result.setFinishPaper(paperRecordsService.userFinishPaperCount(userId, categoryId));
            result.setNoteCount(noteService.selectCount(noteService.wrappers()
                    .eq(BSDUAbsEntity::getCreatedBy, userId)
                    .eq(NoteModel::getCategoryId, categoryId)
            ).intValue());
        });
        var dto = recordsGetDataService.getCategoryCountRecordsResult(userId, industryCode, examId, categoryId);
        f.get();
        f1.get();
        BeanUtil.copyProperties(dto, result);
        return result;
    }


    @GetMapping("/user-count-records")
    @Operation(summary = "获取用户累计做题")
    public UserCountRecordsModel userCountRecords() {
        return userCountRecordsService.userTodayData(userContext.currentUserId());
    }


    @GetMapping("/chapter-records")
    @Operation(summary = "获取章节的做题数统计")
    public RecordsCountRecordDto chapterRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String chapterId,
            @RequestParam(defaultValue = "chapter") RecordsCollectType recordsType
    ) {
        return recordsGetDataService.getCollectCountRecordsResult(userContext.currentUserId(), industryCode, examId, categoryId, recordsType, chapterId);
    }

    @GetMapping("/chapter-records-ids")
    @Operation(summary = "获取章节的做题数统计-章节ids集合")
    public Map<String,RecordsCountRecordDto> chapterRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam List<String> chapterIds,
            @RequestParam(defaultValue = "chapter") RecordsCollectType recordsType
    ) {

        Map<String,RecordsCountRecordDto> map = new HashMap<>();
        for (var chapterId : chapterIds) {
            map.put(chapterId,recordsGetDataService.getCollectCountRecordsResult(userContext.currentUserId(), industryCode, examId, categoryId, recordsType, chapterId));
        }
        return map;
    }


    @Parameters(value = {
            @Parameter(name = "rankType", description = "数据类型()", required = true),
            @Parameter(name = "chapterId", description = "父级章节", required = true),
    })
    @GetMapping("/chapter-records-tree")
    @Operation(summary = "获取集合试题的做题记录归类")
    public List<CollectClassifyRecordsVo> chapterRecordsTree(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam QuestionCollectionType collectionType,
            @RequestParam(required = false) String chapterId,
            @RequestParam RankType type,
            String industryName,
            String examName,
            String categoryName,
            @RequestParam String columnId

    ) {
        String userId = userContext.currentUserId();
        List<Object> models = new LinkedList<>();
        @Getter
        @Setter
        class O implements IBSEntity<O, String>, NameModel<O, String> {
            O(String i, String n, QuestionCollectionType c) {
                id = i;
                name = n;
                ct = c;
            }

            private String id;
            private String name;

            private QuestionCollectionType ct;
        }
        Map<String, String> paperPassive = new HashMap<>();
        if (collectionType == QuestionCollectionType.chapter) {
            List<ChapterModel> list;
            if (StrUtil.isNotEmpty(chapterId)) {
                list = chapterService.selectNextChildren(chapterId);
            } else {
               // list = chapterService.categoryTopChapter(categoryId);
                 list = columnConfigReService.getChaptersByColumn(columnId);
                 list = new ArrayList<>(list);
            }
            list.sort(Comparator.comparing(ChapterModel::getSort));
            models.addAll(list);
        } else if (collectionType == QuestionCollectionType.paper) {
            if (type == RankType.collect) {
//                var allLikeModel = materialLikeService.selectList(
//                        materialLikeService.wrappers().select(MaterialLikeModel::getRelationId, MaterialLikeModel::getRecordsType, MaterialLikeModel::getPassive)
//                                .eq(MaterialLikeModel::getCategoryId, categoryId)
//                                .in(MaterialLikeModel::getRecordsType,
//                                        RecordsCollectType.paperRecordPaperLibrary,
//                                        RecordsCollectType.paperRecordChapterPractice,
//                                        RecordsCollectType.paperRecordSpecialPractice,
//                                        RecordsCollectType.paperRecordRandomFormPaper,
//                                        RecordsCollectType.paperRecordErrorCombination
//                                )
//                                .eq(MaterialLikeModel::getType, 1)
//                                .eq(BSDUAbsEntity::getCreatedBy, userId)
//                );
//                var paperIds = new LinkedList<String>();
//                var recordsIds = new LinkedList<String>();
//                for (var m : allLikeModel) {
//                    if (m.getRecordsType() == RecordsCollectType.paperRecordPaperLibrary) {
//                        paperIds.add(m.getRelationId());
//                    } else {
//                        recordsIds.add(m.getRelationId());
//                    }
//                    if (m.getPassive() == 1) {
//                        paperPassive.put(m.getRelationId(), "");
//                    }
//                }
//                if (!paperIds.isEmpty()) models.addAll(paperService.selectBatchIds(paperIds));
//                if (!recordsIds.isEmpty()) models.addAll(paperRecordsService.selectBatchIds(recordsIds));
            } else {
//                models.addAll(paperRecordsService.selectList(paperRecordsService.wrappers()
//                                .eq(PaperRecordsModel::getUserId, userId)
//                                .ne(PaperRecordsModel::getType, PaperRecordType.chapterPractice)
//                                .eq(PaperRecordsModel::getCategoryId, categoryId)
//                                .in(PaperRecordsModel::getPaperId,paperService.paperByColumnIdPage(Page.of(1,1000), MapUtil.<String, Object>builder()
//                                        .put("columnIds", ListUtil.of(columnId)).build()).getRecords().stream().map(PaperModel::getId).collect(Collectors.toList()))
//                                .orderByDesc(PaperRecordsModel::getModifyTime)
//                        )
//                );


            }
            models.addAll(paperService.paperByColumnIdPage(Page.of(1,1000), MapUtil.<String, Object>builder()
                    .put("columnIds", ListUtil.of(columnId)).build()).getRecords());
        } else {
            models.addAll(new LinkedList<>() {{
                add(new O(RecordsCollectType.rankError.staticCid(null), otherCollectName(industryName, examName, categoryName, LocalDate.now(), RecordsCollectType.rankError), QuestionCollectionType.rankError));
                add(new O(RecordsCollectType.hotQue.staticCid(null), otherCollectName(industryName, examName, categoryName, LocalDate.now(), RecordsCollectType.hotQue), QuestionCollectionType.hotQue));
                add(new O(RecordsCollectType.rankCollect.staticCid(null), otherCollectName(industryName, examName, categoryName, LocalDate.now(), RecordsCollectType.rankCollect), QuestionCollectionType.rankCollect));
            }});
            models.addAll(everyDayPracticeService.selectList(everyDayPracticeService.wrappers().
                    eq(EveryDayPracticeModel::getUserId, userId)
                    .eq(EveryDayPracticeModel::getCategoryId, categoryId)
            ));
        }
        NewQuestionRecordsDto recordsResult = new NewQuestionRecordsDto();
        if (collectionType != QuestionCollectionType.chapter) {
            if (type == RankType.collect) {
                recordsResult = recordsGetDataService.getCollectRecordsResult(userId, industryCode, examId, categoryId);
            } else if (type == RankType.error) {
                recordsResult = recordsGetDataService.getErrorRecordsResult(userId, industryCode, examId, categoryId);
            }
        }
        var cids = recordsResult.getData()
                .values()
                .stream().map(v -> {
                    if (v instanceof JSONObject jv) {
                        return jv.getString("cid");
                    }
                    return null;
                })
                .filter(Objects::nonNull)
                .distinct()
                .toList();
        return models.parallelStream().map(v -> {

            String id = null;
            if (v instanceof IBSEntity<?, ?> ib) {

                id = ib.getId().toString();
                System.out.println(id);
            }
            var rt = RecordsCollectType.collectType2Type(collectionType);
            if (v instanceof PaperModel pr) {
                //rt = RecordsCollectType.paperRecordType2Type(pr.getType());
                rt = RecordsCollectType.paperRecordChapterPractice;
            }

            if (id == null ||  (collectionType != QuestionCollectionType.chapter && !cids.contains(id)) )
                return null;

            if (v instanceof EveryDayPracticeModel) {
                rt = RecordsCollectType.everyday;
            }
            if (v instanceof O o) {
                rt = RecordsCollectType.collectType2Type(o.ct);
            }
            RecordsCountRecordDto dto;
            if (type == RankType.collect && v instanceof PaperModel) {
                rt = RecordsCollectType.paperRecordChapterPractice;
                dto = new RecordsCountRecordDto();
                dto.setCc(recordsGetDataService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, RecordsCollectType.paperRecordChapterPractice, id).getData().size());
            } else {
                if (rt == null) return null;
                if (v instanceof PaperModel pv && type != RankType.collect) {
//                    dto = new RecordsCountRecordDto();
//                    dto.setDc(pv.getDoCount());
//                    dto.setEc(pv.getErrorCount());
                    dto = recordsGetDataService.getCollectCountRecordsResult(userId, industryCode, examId, categoryId, rt, id);
                } else {
                    dto = recordsGetDataService.getCollectCountRecordsResult(userId, industryCode, examId, categoryId, rt, id);
                }
            }
            if (type == RankType.all && dto.getDc() == 0) {
                return null;
            }
            if (type == RankType.error && dto.getEc() == 0) {
                return null;
            }
            if (type == RankType.collect && dto.getCc() == 0 && !paperPassive.containsKey(id)) {
                return null;
            }

            var vo = new CollectClassifyRecordsVo();
            vo.setRecordsType(rt);
            vo.setCategoryId(categoryId);
            vo.setDc(dto.getDc());
            vo.setEc(dto.getEc());
            if (type == RankType.collect && paperPassive.containsKey(id)) {
                vo.setCc(0);
            } else {
                vo.setCc(dto.getCc());
            }
            vo.setId(id);
            if (v instanceof ChapterModel ch) {
                vo.setLeaf(ch.getLeaf());
            }
            if (v instanceof NameModel n) {
                vo.setName(n.getName());
            }
            if (v instanceof PaperModel p) {
                vo.setPaperType(p.getPaperType());
            }
            if (v instanceof PaperRecordsModel pr && (pr.getType() == PaperRecordType.paperLibrary || pr.getType() == PaperRecordType.chapterPractice)) {
                var s = paperService.getById(pr.getPaperId());
                if (s != null) {
                    vo.setPaperType(s.getPaperType());
                    vo.setPaperId(s.getId());
                }
            }
            return vo;
        }).filter(Objects::nonNull).toList();
    }

    @GetMapping("/paperRecordsCollect")
    @Operation(summary = "获取试卷收藏")
    @Roles
    public List<CollectClassifyRecordsVo> paperRecordsCollect(@RequestParam String industryCode, @RequestParam String examId, @RequestParam Long categoryId) {
        return chapterRecordsTree(industryCode, examId, categoryId, QuestionCollectionType.paper, null, RankType.collect, null, null, null,null);
    }

    @GetMapping("/category-news-error")
    @Operation(summary = "获取科目下最新错题")
    public IPage<NewRecordsDtoItemVo> categoryNewError(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            int current,
            @RequestParam(defaultValue = "20") Integer size
    ) {
//NewRecordsDtoItemVo
        return recordsGetDataService.getErrorRecordsResult(userContext.currentUserId(), industryCode, examId, categoryId, current, size);
    }

    @GetMapping("/category-news-collect")
    @Operation(summary = "获取科目下最新收藏")
    public IPage<NewRecordsDtoItemVo> categoryNewCollect(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            int current,
            @RequestParam(defaultValue = "20") Integer size
    ) {
        return recordsGetDataService.getCollectRecordsResult(userContext.currentUserId(), industryCode, examId, categoryId, current, size);
    }

    @GetMapping("/category-rank")
    @Operation(summary = "获取考试或者科目下做题排做题")
    @Roles
    public IPage<ExamQuestionRank.ExamQuestionRankBaseModel> categoryRank(
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @ModelAttribute Page<ExamQuestionRank.ExamQuestionRankBaseModel> page,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) String qid
    ) {
        return examQuestionRankService.rankData(examId, categoryId, page, RankType.all, keyword, qid);
    }


    @GetMapping("/category-rank-error")
    @Operation(summary = "获取考试或者科目下错题排行")
    @Roles
    public IPage<ExamQuestionRank.ExamQuestionRankBaseModel> categoryRankError(
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @ModelAttribute Page<ExamQuestionRank.ExamQuestionRankBaseModel> page,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) String qid
    ) {
        return examQuestionRankService.rankData(examId, categoryId, page, RankType.error, keyword, qid);
    }

    @GetMapping("/category-rank-error-kaoyan")
    @Operation(summary = "获取考试或者科目下错题排行")
    @Roles
    @ResultIgnore
    public IPage<KaoYanErrorQuestionModel> categoryRankErrorkaoyan(
            @RequestParam(required = false) List<Long> categoryIds,
            @RequestParam Integer current,
            @RequestParam Integer size,
            @RequestParam(required = false) String questionType
    ) {
        Criteria criteria = new Criteria();
        if (null != categoryIds && !categoryIds.isEmpty()) {
            criteria.and("categoryId").in(categoryIds);

        }
        if (StrUtil.isNotBlank(questionType)) {
            criteria.and("type").is(questionType);
        }
        Sort sort = Sort.by(Sort.Direction.DESC, "count");
        Query query = new Query(criteria).with(sort);
        // 设置起始页和每页查询条数
        Pageable pageable = PageRequest.of(current - 1, size);
        // 查询记录总数
        long totalCount = mongoTemplate.count(query, KaoYanErrorQuestionModel.class);
        //查询分页后的记录
        List<KaoYanErrorQuestionModel> list = mongoTemplate.find(query.with(pageable), KaoYanErrorQuestionModel.class);
        // 计算总页数
        Page<KaoYanErrorQuestionModel> modelPage = new Page<>();
        modelPage.setCurrent(current);
        modelPage.setTotal(totalCount);
        modelPage.setSize(size);
        modelPage.setRecords(list);
        return modelPage;
    }

    @GetMapping("/category-rank-error-kaoyan-type")
    @Operation(summary = "获取考试或者科目下错题排行")
    @Roles
    @ResultIgnore
    public List<String> categoryRankErrorType(
            @RequestParam(required = false) List<Long> categoryIds
    ) {

        Criteria criteria = new Criteria();
        // 主表可能选择的条件
        if (null != categoryIds && !categoryIds.isEmpty()) {
            criteria.and("categoryId").in(categoryIds);
        }

        Query query = new Query(criteria);
        query.fields().include("type");
        return mongoTemplate.findDistinct(query, "type", KaoYanErrorQuestionModel.class, String.class);
    }

    @GetMapping("/category-rank-collect")
    @Operation(summary = "获取考试或者科目下收藏排行")
    @Roles
    public IPage<ExamQuestionRank.ExamQuestionRankBaseModel> categoryRankCollect(
            @RequestParam String examId,
            Long categoryId,
            @ModelAttribute Page<ExamQuestionRank.ExamQuestionRankBaseModel> page,
            String keyword,
            String qid
    ) {
        return examQuestionRankService.rankData(examId, categoryId, page, RankType.collect, keyword, qid);
    }

    @GetMapping("/collect-question-records")
    @Operation(summary = "获取集合的做题记录")
    public QuestionRecordsDto collectQuestionRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam RecordsCollectType recordsType,
            @RequestParam String collectId
    ) {
        return recordsGetDataService.getQuestionRecordsResult(userContext.currentUserId(), industryCode, examId, categoryId, recordsType, collectId);
    }

    @GetMapping("/collection-collect-records")
    @Operation(summary = "获取集合的收藏记录")
    public CollectionErrorAndCollectRecordDataDto collectionCollectRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam RecordsCollectType recordsType,
            @RequestParam String collectId
    ) {
        return recordsGetDataService.getCollectInCollectDataResult(userContext.currentUserId(), industryCode, examId, categoryId, recordsType, collectId);
    }


    //    push数据部分

    @Operation(summary = "提交用户做题答案，校验答案正确性")
    @Parameters(value = {
            @Parameter(name = "collectId", description = "集合id(每日一练记录id/章节id/试卷id)", required = true),

    })
    @PostMapping("/push-question-right-check")
    @EncryptionApi
    @Roles
    public QuestionRecordsDtoItem questionCheck(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String collectId,
            @RequestParam String qid,
            @RequestParam QuestionType type,
            String as,
            @RequestParam(required = false) String answer,
            @RequestParam(required = false) String clientRightAnswer
    ) throws QPSFailException, InterruptedException {
        String userId = userContext.currentUserId();
        if (!QuestionUtil.questionIdIsCombinationChild(qid)) {
            var k = userContext.loginStatus() ? userContext.currentUserId() : userContext.getDuid();
            memoryRateLimiterBean.acquire(new RateLimiterParams()
                    .setKey(k)
                    .setNamespace("exam:safe:")
                    .setMaxRate(4)
                    .setAcquireCount(2)
                    .setMillisecond(1000)
                    .setRecoveryCount(2)
                    .setRejectStrategy(RejectStrategy.wait)
            );
        }


        Object rightAnswer;
        if (StrUtil.isNotBlank(clientRightAnswer)) {
            rightAnswer = checkAnswerType(type,clientRightAnswer);
        } else {
            rightAnswer = checkAnswerType(type, questionService.answerDecrypt(answer, StrUtil.isEmpty(userId) ? userContext.getDuid() : userId, collectId).toString());
        }
        var dto = new QuestionRecordsDtoItem();

        if (StrUtil.isNotBlank(as)) {

            var isRight = questionDataProcessComposite.userAnswerIsRight(type, rightAnswer,checkAnswerType(type,as));
            QuestionExpandModel questionExpand = questionExpandService.getById(qid);
            questionExpand.setDoingNumber(questionExpand.getDoingNumber()+1);
            questionExpand.setErrorNumber(!isRight ? questionExpand.getErrorNumber()+1 : questionExpand.getErrorNumber());
            questionExpandService.update(questionExpand);
            dto.setRt(isRight);
        }
        dto.setAs(rightAnswer);
        return dto;
    }
    Object checkAnswerType(QuestionType type, String as){
        Object Oas = null;
        switch (type) {
            case singleChoice, fill, text -> {
                Oas = as;
            }
            case judgment -> {
                Oas = Integer.parseInt(as);
            }
            case multipleChoice ->{
                Oas = JSON.parse(as);
            }
            case indefiniteMultipleChoice -> {
                if (!JSONUtil.isJson(as)) {
                    return as;
                }
                Oas = JSON.parse(as);
            }
        }
        return Oas;
    }



    @PostMapping("/push-question-answer")
    @Parameters(value = {
            @Parameter(name = "industryCode", description = "行业code", required = true),
            @Parameter(name = "examId", description = "考试id", required = true),
            @Parameter(name = "categoryId", description = "科目id", required = true),
            @Parameter(name = "qid", description = "试题id", required = true),
            @Parameter(name = "data", description = "试题数据", required = true, schema = @Schema(implementation = NewRecordsDtoItem.class)),
            @Parameter(name = "rightAnswer", description = "试题正确答案", required = true),
            @Parameter(name = "rightCount", description = "正确几次移除错题记录", required = true),
    })

    @Operation(summary = "提交单个试题做题记录 适用于我的错题重做，我的收藏，高频错题做题 ", description = "返回t的描述：-1 无操作 1 以前对 现在错 2以前错现在对 3新纪录对 4新纪录错 5移除错题")
    @RateLimiterApi(namespace = "exam:safe:", maxRate = 2, recoveryCount = 2, target = RateLimiterApiTarget.user, rejectStrategy = RejectStrategy.wait, bean = "memory")
    public QuestionRecordsDtoItem pushQuestionAnswer(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String qid,
            @ModelAttribute("data") NewRecordsDtoItem data,
            @ModelAttribute("rightAnswer") Object rightAnswer,
            @ModelAttribute("rightAnswerBase64") String rightAnswerBase64,
            @RequestParam(defaultValue = "0") Integer rightCount,
            String industryName,
            String examName,
            String categoryName,
            @RequestParam RecordsCollectType recordsType
    ) {
        var collectName = data.getCn();
        var collectId = data.getCid();
        if (recordsType == RecordsCollectType.rankError || recordsType == RecordsCollectType.rankCollect || recordsType == RecordsCollectType.hotQue) {
            collectName = otherCollectName(industryName, examName, categoryName, LocalDate.now(), recordsType);
            collectId = recordsType.staticCid(data.getCid());
        }
        var dataCopy = new NewRecordsDtoItem();
        BeanUtil.copyProperties(data, dataCopy);
        dataCopy.setCid(collectId);
        dataCopy.setCn(collectName);
        dataCopy.setCt(recordsType);
        Assert.notBlank(collectId, "章节不能为空");
        Assert.notBlank(collectName, "章节名称不能为空");
        Assert.notNull(data.getAs(), "请填写答案");
        Assert.notNull(data.getQt(), "题型不能为空");
        var userId = userContext.currentUserId();
        Long localGroupId = userContext.currentLocalGroupId();
        if (rightAnswer == null) {
            rightAnswer = questionService.answerDecrypt(rightAnswerBase64, StrUtil.isEmpty(userId) ? localGroupId.toString() : userId,
                    StrUtil.isEmpty(userId) ? localGroupId.toString() : userId);
        }
        var right = questionDataProcessComposite.userAnswerIsRight(dataCopy.getQt(), rightAnswer, dataCopy.getAs());
        SysThreadPool.execute(() -> {
            userCountRecordsService.addCount(userId, right ? 0 : 1, 1);
            if (data.getCt() == RecordsCollectType.chapter || data.getCt() == RecordsCollectType.paperRecordPaperLibrary || data.getCt() == RecordsCollectType.paperRecordChapterPractice) {
                var rankData = ExamQuestionRank.ExamQuestionRankBaseModel.builder()
                        .examId(examId)
                        .build();
                rankData.setCategoryId(categoryId);
                rankData.setDoCount(1L);
                rankData.setErrorCount(right ? 0L : 1L);
                rankData.setQuestionId(qid);
                rankData.setQuestionSign(data.getSn());
                rankData.setCollectName(data.getCn());
                rankData.setType(data.getTr());
                rankData.setCollectType(data.getCt().type2CollectType());
                rankData.setCollectId(data.getCid());
                rankData.setStem(data.getSt());
                examQuestionRankService.addDoCount(rankData, data.getCt());
            }
            weekRankService.addData(userId, categoryId, 1, right ? 0 : 1);
        });
        var dto = new QuestionRecordsDtoItem();
        if (recordsType.type2CollectType() != QuestionCollectionType.paper) {
            if (recordsType == RecordsCollectType.rankError || recordsType == RecordsCollectType.rankCollect || recordsType == RecordsCollectType.hotQue) {
                SysThreadPool.execute(() -> {
                    var categoryCollectRecordsDtoItem = new CategoryCollectRecordsDtoItem();
                    categoryCollectRecordsDtoItem.setN(dataCopy.getCn());
                    categoryCollectRecordsDtoItem.setT(System.currentTimeMillis());
                    categoryCollectRecordsDtoItem.setTc(1);
                    categoryCollectRecordsDtoItem.setDc(1);
                    recordsService.addCategoryNewCollectRecords(categoryCollectRecordsDtoItem, dataCopy.getCt(), dataCopy.getCid(), userId, industryCode, examId, categoryId);
                });
            }
            dto.setT(recordsService.userPushOneQuestionRecords(userContext.currentUserId(), industryCode, examId, categoryId, qid, dataCopy, right, rightCount));
        } else {
            // -1 无操作 2以前错现在对  5移除错题
            var errorData = recordsGetDataService.getErrorInCollectDataResult(userId, industryCode, examId, categoryId, recordsType, dataCopy.getCid());
            var oldData = errorData.get(qid);
            if (oldData != null) {
                if (right) {
                    oldData++;
                    errorData.getData().put(qid, oldData);
                    recordsSetDataService.setErrorInCollectData(userId, errorData, industryCode, examId, categoryId, recordsType, dataCopy.getCid());
                    // 2以前错现在对
                    dto.setT(2);
                }
                if (rightCount > 0 && oldData >= rightCount) {
                    dto.setT(5);
                    var allErrorData = recordsGetDataService.getErrorRecordsResult(userId, industryCode, examId, categoryId);
                    allErrorData.remove(qid);
                    recordsSetDataService.setErrorRecordsData(userId, allErrorData, industryCode, examId, categoryId);
                }
            } else {
                dto.setT(-1);
            }
        }
        dto.setRt(right);
        dto.setAs(rightAnswer);
        userQuestionRecordsService.setQuestionRecords(userId, categoryId, dataCopy.getTr(), 1, right ? 0 : 1);
        return dto;
    }

    @PostMapping("/push-question-collect")
    @Operation(summary = "提交单个试题的收藏")
    public boolean pushQuestionCollect(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String qid,
            String industryName,
            String examName,
            String categoryName,
            @ModelAttribute("data") NewRecordsDtoItem data,
            @RequestParam RecordsCollectType recordsType
    ) {
        var dataCopy = new NewRecordsDtoItem();
        BeanUtil.copyProperties(data, dataCopy);
        dataCopy.setCid(data.getCid());
        dataCopy.setCn(data.getCn());
        dataCopy.setCt(recordsType);
        if (recordsType == RecordsCollectType.rankError || recordsType == RecordsCollectType.rankCollect || recordsType == RecordsCollectType.hotQue) {
            dataCopy.setCn(otherCollectName(industryName, examName, categoryName, LocalDate.now(), recordsType));
            dataCopy.setCid(recordsType.staticCid(data.getCid()));
        }
        var userId = userContext.currentUserId();
        Assert.notBlank(dataCopy.getCid(), "章节不能为空");
        Assert.notBlank(dataCopy.getCn(), "章节名称不能为空");
        Assert.notNull(dataCopy.getQt(), "题型不能为空");
        var map = recordsGetDataService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, recordsType, dataCopy.getCid());
        var da = map.get(qid);
        if (da != null) {
            map.remove(qid);
        } else {
            map.put(qid, dataCopy.getT());
        }
        var newMap = recordsGetDataService.getCollectRecordsResult(userId, industryCode, examId, categoryId);
        if (da == null) {
            newMap.put(qid, dataCopy);
        } else {
            newMap.remove(qid);
        }
        recordsSetDataService.setCollectInCollectionData(userId, map, industryCode, examId, categoryId, recordsType, dataCopy.getCid());
        recordsSetDataService.setCollectRecordsData(userId, newMap, industryCode, examId, categoryId);
        var dto = new RecordsCountRecordDto();
        dto.setCc(da == null ? 1 : -1);
        if (recordsType.type2CollectType() == QuestionCollectionType.paper) {
            if (newMap.size() == 0) {
                materialLikeService.delCollectionQuestions(userId, industryCode, examId, categoryId, dataCopy.getCid(), dataCopy.getCt());
            } else {
                materialLikeService.addCollectionQuestions(userId, industryCode, examId, categoryId, dataCopy.getCid(), dataCopy.getCt());
            }
        }
        recordsService.collectCountRecordsChange(userId, industryCode, examId, categoryId, dataCopy.getCt(), dataCopy.getCid(), dto);
        if (da == null && (data.getCt() == RecordsCollectType.chapter || data.getCt() == RecordsCollectType.paperRecordPaperLibrary)) {
            SysThreadPool.execute(() -> {
                var rankData = ExamQuestionRank.ExamQuestionRankBaseModel.builder()
                        .examId(examId)
                        .build();
                rankData.setQuestionId(qid);
                rankData.setQuestionSign(data.getSn());
                rankData.setCollectName(data.getCn());
                rankData.setCollectType(data.getCt().type2CollectType());
                rankData.setCollectId(data.getCid());
                rankData.setStem(data.getSt());
                rankData.setType(data.getTr());
                rankData.setCategoryId(categoryId);
                rankData.setCollectCount(1L);
                examQuestionRankService.addDoCount(rankData, data.getCt());
            });
        }
        return da == null;
    }

    @PostMapping("/push-chapter-answer")
    @Parameters(value = {
            @Parameter(name = "recordsType", description = "集合类型 章节练习 每日一练", required = true),
            @Parameter(name = "industryCode", description = "行业code", required = true),
            @Parameter(name = "examId", description = "考试id", required = true),
            @Parameter(name = "categoryId", description = "科目id", required = true),
            @Parameter(name = "collectId", description = "章节id或试卷Id", required = true),
            @Parameter(name = "lastId", description = "最后做题id", required = true),
            @Parameter(name = "collectName", description = "章节名称或者试卷名称"),
            @Parameter(name = "totalCount", description = "当前试题总数"),
    })
    @Operation(summary = "练习模式退出时提交答案")
    public void practicePushAnswer(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String collectId,
            @RequestParam RecordsCollectType recordsType,
            @RequestParam String collectName,
            @RequestParam String lastId,
            @RequestParam Integer totalCount,
            @ModelAttribute("data") Map<String, ApiQuestionRecordsDtoItem> data
    ) {
        if (CollUtil.isEmpty(data)) return;
        var userId = userContext.currentUserId();
        var dto = new QuestionRecordsDto();
        data.forEach((k, v) -> dto.put(k, v.toQuestionRecordsDtoItem()));
        dto.getData().put(QuestionRecordsDto.LAST_ID, lastId);
        // 提交章节记录
        recordsSetDataService.setQuestionRecordsData(userId, dto, industryCode, examId, categoryId, recordsType, collectId);
        // 保存最新最提记录
        var categoryCollectRecordsDtoItem = new CategoryCollectRecordsDtoItem();
        categoryCollectRecordsDtoItem.setN(collectName);
        categoryCollectRecordsDtoItem.setT(System.currentTimeMillis());
        categoryCollectRecordsDtoItem.setTc(totalCount);
        categoryCollectRecordsDtoItem.setDc(data.size());
        categoryCollectRecordsDtoItem.setEc((int) data.values().stream().filter(v -> !v.isRt()).count());
        recordsService.addCategoryNewCollectRecords(categoryCollectRecordsDtoItem, recordsType, collectId, userId, industryCode, examId, categoryId);
        // 保存做题排行
        if (data.values().stream().anyMatch(ApiQuestionRecordsDtoItem::isNewDo)) {
            SysThreadPool.execute(() -> {
                Map<String, Integer> all = new HashMap<>();
                Map<String, Integer> error = new HashMap<>();
                data.forEach((k, v) -> {
                    if (v.isNewDo()) {
                        var rankData = ExamQuestionRank.ExamQuestionRankBaseModel.builder()
                                .examId(examId)
                                .build();
                        rankData.setCategoryId(categoryId);
                        rankData.setDoCount(1L);
                        rankData.setErrorCount(v.isRt() ? 0L : 1L);
                        rankData.setQuestionId(k);
                        rankData.setQuestionSign(v.getSn());
                        rankData.setCollectName(collectName);
                        rankData.setCollectType(recordsType.type2CollectType());
                        rankData.setCollectId(collectId);
                        rankData.setType(v.getTr());
                        rankData.setStem(v.getSt());
                        examQuestionRankService.addDoCount(rankData, recordsType);
                        var o = all.get(v.getTr());
                        all.put(v.getTr(), o == null ? 1 : o + 1);
                        if (!v.isRt()) {
                            o = error.get(v.getTr());
                            error.put(v.getTr(), o == null ? 1 : o + 1);
                        }
                    }
                });
                all.forEach((key, val) -> userQuestionRecordsService.setQuestionRecords(userId, categoryId, key, val, error.get(key) == null ? 0 : error.get(key)));
                var dc = (int) data.values().stream().filter(ApiQuestionRecordsDtoItem::isNewDo).count();
                var ec = (int) data.values().stream().filter(v -> v.isNewDo() && !v.isRt()).count();
                weekRankService.addData(userId, categoryId, dc, ec);
                userCountRecordsService.addCount(userId, ec, dc);
            });
        }
    }

    @Operation(summary = "保存试卷章节练习模式记录")
    @PostMapping("/save-paper-practice-record")
    public void savePaperChapterPractice(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String recordsId,
            @RequestParam String collectName,
            @RequestParam Integer totalCount
    ) {
        var categoryCollectRecordsDtoItem = new CategoryCollectRecordsDtoItem();
        categoryCollectRecordsDtoItem.setN(collectName);
        categoryCollectRecordsDtoItem.setT(System.currentTimeMillis());
        categoryCollectRecordsDtoItem.setTc(totalCount);
        categoryCollectRecordsDtoItem.setDc(1);
        recordsService.addCategoryNewCollectRecords(categoryCollectRecordsDtoItem, RecordsCollectType.paperRecordChapterPractice, recordsId, userContext.currentUserId(), industryCode, examId, categoryId);
    }

    @PostMapping("/save-paper-answer")
    @Parameters(value = {
            @Parameter(name = "industryCode", description = "行业code", required = true),
            @Parameter(name = "examId", description = "考试id", required = true),
            @Parameter(name = "categoryId", description = "科目id", required = true),
            @Parameter(name = "chapterId", description = "章节id", required = true),
            @Parameter(name = "lastId", description = "最后做题id", required = true),
            @Parameter(name = "data", description = "章节做题记录{key:试题id，value：item}", required = true),
            @Parameter(name = "item", description = "item"),
    })
    @Operation(summary = "保存试卷做题记录，只是保存，不交卷")
    public void savePaperAnswer(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String recordsId,
            @RequestParam String lastId,
            @RequestParam Long seconds,
            @RequestParam String cn,
            @RequestParam Integer totalCount,
            @RequestParam RecordsCollectType recordsType,
            @ModelAttribute("data") Map<String, QuestionRecordsDtoItem> data
    ) {
        var userId = userContext.currentUserId();
        // 保存最新最提记录
        var categoryCollectRecordsDtoItem = new CategoryCollectRecordsDtoItem();
        categoryCollectRecordsDtoItem.setN(cn);
        categoryCollectRecordsDtoItem.setT(System.currentTimeMillis());
        categoryCollectRecordsDtoItem.setTc(totalCount);
        categoryCollectRecordsDtoItem.setDc(data.size());
        categoryCollectRecordsDtoItem.setEc(0);
        recordsService.addCategoryNewCollectRecords(categoryCollectRecordsDtoItem, recordsType, recordsId, userId, industryCode, examId, categoryId);
        recordsService.userSavePaperRecords(userId, industryCode, examId, categoryId, recordsId, data, seconds, lastId);
    }

    @PostMapping("/submit-paper-answer")
    @Parameters(value = {
            @Parameter(name = "industryCode", description = "行业code", required = true),
            @Parameter(name = "examId", description = "考试id", required = true),
            @Parameter(name = "categoryId", description = "科目id", required = true),
            @Parameter(name = "recordsId", description = "做卷记录id", required = true),
            @Parameter(name = "lastId", description = "最后做题id", required = true),
            @Parameter(name = "data", description = "章节做题记录{key:试题id，value：item}", required = true),
            @Parameter(name = "seconds", description = "data"),
    })
    @Operation(summary = "试卷交卷")
    public PaperResultVo submitPaperAnswer(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String recordsId,
            @RequestParam Long seconds,
            @ModelAttribute("data") Map<String, ApiQuestionRecordsDtoItem> data,
            boolean imageCheck,
            Long timestamp,
            String sign,
            HttpServletRequest request
    ) throws QPSFailException, InterruptedException {
        var userId = userContext.currentUserId();
        Long localGroupId = userContext.currentLocalGroupId();
        var recordsModel = paperRecordsService.getById(recordsId);
        if (recordsModel.getType() == PaperRecordType.paperLibrary) {
            int count = 1;
            if (null != recordsModel.getCompleteCount()) {
                count = recordsModel.getCompleteCount() + 1;
            }
            paperRecordsService.update(null, Wrappers.<PaperRecordsModel>lambdaUpdate().eq(PaperRecordsModel::getUserId, userId).eq(PaperRecordsModel::getType, PaperRecordType.paperLibrary).eq(PaperRecordsModel::getPaperId, recordsModel.getPaperId()).set(PaperRecordsModel::getCompleteCount, count));
            if (paperRecordsService.selectCount(paperRecordsService.wrappers()
                    .eq(PaperRecordsModel::getUserId, userId)
                    .eq(PaperRecordsModel::getPaperId, recordsModel.getPaperId())
                    .eq(PaperRecordsModel::getType, recordsModel.getType())
                    .isNotNull(PaperRecordsModel::getEndTime)
            ) == 0) {
                try {
                    rateLimiter.acquire(new RateLimiterParams()
                            .setKey(userId)
                            .setNamespace("exam:safe-submit-paper:")
                            .setMaxRate(10)
                            .setMillisecond(localGroupId != null ? diamondConfig.getGroupSubmitPaperTime() : diamondConfig.getOtherSubmitPaperTime())
                    );
                } catch (QPSFailException e) {
                    systemService.aiCheck(imageCheck, request, timestamp, sign);
                    rateLimiter.acquire(new RateLimiterParams()
                            .setKey(userId)
                            .setNamespace("exam:safe-submit-paper-two:")
                            .setMaxRate(30)
                            .setAcquireCount(6)
                            .setMillisecond(5000)
                            .setRejectStrategy(RejectStrategy.wait)
                            .setWaitTime(20000)
                    );
                } catch (InterruptedException ignore) {
                }
            }
        }
        data.entrySet().parallelStream().forEach(entity -> {
            var value = entity.getValue();
            value.setRt(recordsService.userAnswerIsRight(value.getQt(), userId, recordsId, value.getAsb(), checkAnswerType(value.getQt(), (String) value.getAs())));
        });
        return recordsService.userPushPaperRecords(userId, industryCode, examId, categoryId, recordsId, data, LocalDateTime.now(), seconds);
    }


    @PostMapping("/public-submit-paper-answer")
    @Parameters(value = {
            @Parameter(name = "industryCode", description = "行业code", required = true),
            @Parameter(name = "examId", description = "考试id", required = true),
            @Parameter(name = "categoryId", description = "科目id", required = true),
            @Parameter(name = "chapterId", description = "章节id", required = true),
            @Parameter(name = "lastId", description = "最后做题id", required = true),
            @Parameter(name = "data", description = "章节做题记录{key:试题id，value：item}", required = true),
            @Parameter(name = "item", description = "item"),
    })
    @Operation(summary = "未登录交卷试卷交卷")
    @Roles
    public PaperResultVo submitPublicPaperAnswer(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam Long seconds,
            @ModelAttribute("data") Map<String, ApiQuestionRecordsDtoItem> data,
            @ModelAttribute("questionCollectVo") QuestionCollectVo questionCollectVo,
            @ModelAttribute("recordsModel") PaperRecordsModel recordsModel,
            boolean imageCheck,
            Long timestamp,
            String sign,
            HttpServletRequest request
    ) throws QPSFailException, InterruptedException {
        String key = userContext.currentIp();
        var localGroupId = userContext.currentLocalGroupId();
        var recordsId = questionCollectVo.getId().toString();
        var map = new HashMap<String, QuestionVo>();
        coreQuestionCollectionService.collectVoForEach(questionCollectVo, v -> map.put(v.getId(), v));
        map.keySet().parallelStream().forEach(k -> {
            var questionVo = map.get(k);
            var apiQuestionRecordsDtoItem = data.get(k);
            if (questionVo.getRightAnswer() == null && localGroupId != null) {
                questionVo.setRightAnswer(questionService.answerDecrypt(questionVo.getAnswerBase64(), localGroupId.toString(), recordsId));
            }
            if (apiQuestionRecordsDtoItem != null) {
                if (questionVo.getRightAnswer() != null) {
                    apiQuestionRecordsDtoItem.setRt(questionDataProcessComposite.userAnswerIsRight(apiQuestionRecordsDtoItem.getQt(), questionVo.getRightAnswer(), apiQuestionRecordsDtoItem.getAs()));
                }
            }
        });
//        data.entrySet().parallelStream().forEach(entity -> {
//            var value = entity.getValue();
//            var vo = map.get(entity.getKey());
//            if (vo.getRightAnswer() != null) {
//                value.setRt(questionDataProcessComposite.userAnswerIsRight(value.getQt(), vo.getRightAnswer(), value.getAs()));
//            } else if (localGroupId != null) {
//                vo.setRightAnswer(questionService.answerDecrypt(vo.getAnswerBase64(), localGroupId.toString(), recordsId));
//                value.setRt(questionDataProcessComposite.userAnswerIsRight(value.getQt(), vo.getRightAnswer(), value.getAs()));
//            }
//        });
        var result = recordsService.userPushPaperRecords(industryCode, examId, categoryId, recordsModel, data, LocalDateTime.now(), seconds);
        result.setCollectStructure(questionCollectVo);
        return result;
    }


    @PostMapping("/push-collect-error-answer")
    @Parameters(value = {
            @Parameter(name = "industryCode", description = "行业code", required = true),
            @Parameter(name = "examId", description = "考试id", required = true),
            @Parameter(name = "categoryId", description = "科目id", required = true),
            @Parameter(name = "collectId", description = "集合id", required = true),
            @Parameter(name = "collectName", description = "集合名称", required = true),
            @Parameter(name = "recordsType", description = "集合类型", required = true),
            @Parameter(name = "data", description = "章节做题记录{key:试题id，value：item}", required = true),
    })
    @Operation(summary = "集合做题退出时保存错题数据")
    public void collectPushError(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String collectId,
            @RequestParam String collectName,
            @RequestParam RecordsCollectType recordsType,
            @ModelAttribute("data") Map<String, NewRecordsDtoItem> data
    ) {
        if (CollUtil.isEmpty(data)) return;
        for (var item : data.values()) {
            item.setCn(collectName);
            item.setCid(collectId);
            item.setCt(recordsType);
        }
        recordsService.pushErrorBlockData(userContext.currentUserId(), industryCode, examId, categoryId, recordsType, collectId, data);
    }

    @PostMapping("/push-new-collect")
    @Parameters(value = {
            @Parameter(name = "industryCode", description = "行业code", required = true),
            @Parameter(name = "examId", description = "考试id", required = true),
            @Parameter(name = "categoryId", description = "科目id", required = true),
            @Parameter(name = "collectId", description = "集合id(章节id或者试卷id)", required = true),
            @Parameter(name = "collectName", description = "集合名称", required = true),
            @Parameter(name = "recordsType", description = "集合类型", required = true),
            @Parameter(name = "removeIds", description = "移除收藏的ids", required = true),
            @Parameter(name = "data", description = "章节新增收藏记录{key:试题id，value：item}", required = true),
            @Parameter(name = "cdata", description = "章节收藏记录{key:试题id，value：时间戳}", required = true),
    })
    @Operation(summary = "集合做题退出时保存试题收藏数据")
    public void collectPushCollect(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String collectId,
            @RequestParam String collectName,
            @RequestParam RecordsCollectType recordsType,
            @RequestParam List<String> removeIds,
            @ModelAttribute("cdata") Map<String, Long> cdata,
            @ModelAttribute("data") Map<String, NewRecordsDtoItem> data
    ) {
        String userId = userContext.currentUserId();
//        recordsType = recordsType == RecordsCollectType.paperRecordChapterPractice ?
//                RecordsCollectType.paperRecordPaperLibrary : recordsType;
        for (var item : data.values()) {
            item.setCn(collectName);
            item.setCid(collectId);
            item.setCt(recordsType);
        }
        if (recordsType != RecordsCollectType.chapter && recordsType != RecordsCollectType.everyday) {
            //var old = recordsGetDataService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, recordsType, collectId);
            //var countData = new RecordsCountRecordDto();
            //countData.setCc(cdata.size() - old.size());
            //recordsService.collectCountRecordsChange(userId, industryCode, examId, categoryId, recordsType, collectId, countData);
        }
        var dto = new CollectionErrorAndCollectRecordDataDto();
        dto.getData().putAll(cdata);
        recordsSetDataService.setCollectInCollectionData(userId, dto, industryCode, examId, categoryId, recordsType, collectId);
        recordsService.pushCollectBlockData(userId, industryCode, examId, categoryId, recordsType, collectId, data, removeIds);
        if (recordsType.type2CollectType() == QuestionCollectionType.paper) {
            if (cdata.size() == 0) {
                materialLikeService.delCollectionQuestions(userId, industryCode, examId, categoryId, collectId, recordsType);
            } else {
                materialLikeService.addCollectionQuestions(userId, industryCode, examId, categoryId, collectId, recordsType);
            }
        }
    }

    @PostMapping("/save-chapter-records")
    @Parameters(value = {
            @Parameter(name = "industryCode", description = "行业code", required = true),
            @Parameter(name = "examId", description = "考试id", required = true),
            @Parameter(name = "categoryId", description = "科目id", required = true),
            @Parameter(name = "collectId", description = "章节id", required = true),
            @Parameter(name = "recordsType", description = "集合类型", required = true),
            @Parameter(name = "dc", description = "新增/减少 做题数量", required = true),
            @Parameter(name = "ec", description = "新增/减少 错题数量", required = true),
            @Parameter(name = "cc", description = "新增/减少 收藏试题数量", required = true),
            @Parameter(name = "data", description = "数据集合", required = true),
    })
    @Operation(summary = "保存章集合统计数据")
    public void saveChapterRecords(@RequestParam String industryCode,
                                   @RequestParam String examId,
                                   @RequestParam Long categoryId,
                                   @RequestParam String collectId,
                                   @RequestParam RecordsCollectType recordsType,
                                   @ModelAttribute("data") RecordsCountRecordDto data) {
        recordsService.collectCountRecordsChange(userContext.currentUserId(), industryCode, examId, categoryId, recordsType, collectId, data);
    }


    //清除相关
    @Operation(summary = "清空试卷记录")
    @DeleteMapping("/clean-paper-records/{id}")
    public void cleanPaperRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @PathVariable String id,
            @RequestParam RecordsCollectType recordsCollectType
    ) {
        recordsDelService.delQuestionRecords(userContext.currentUserId(), industryCode, examId, categoryId, recordsCollectType, id);
    }

    @Operation(summary = "删除集合做题/错题记录")
    @DeleteMapping("/clean-collect-records/{id}")
    public RecordsCountRecordDto cleanCollectRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam RecordsCollectType recordsType,
            @PathVariable String id,
            boolean haveCollect
    ) {
        String userId = userContext.currentUserId();
        // 如果是试卷记录
        if (recordsType.type2CollectType() == QuestionCollectionType.paper) {
            var model = paperRecordsService.getById(id);
            if (!userId.equals(model.getUserId())) throw new BizRuntimeException(AclResultCode.noPower);
            Assert.isTrue(paperRecordsService.delete(id));
            id  = model.getPaperId();
        }
        var r = this.cleanChapterCollectRecords(industryCode, examId, categoryId, id, recordsType, RankType.all, haveCollect);
        recordsService.removeCategoryNewCollectRecords(recordsType, id, userId, industryCode, examId, categoryId);
        return r;
    }


    @Operation(summary = "删除单个错题记录")
    @DeleteMapping("/del-one-error")
    public void cleanOneErrorRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String qid
    ) {
        var userId = userContext.currentUserId();
        var errorData = recordsGetDataService.getErrorRecordsResult(userId, industryCode, examId, categoryId);
        if (errorData.getData().remove(qid) != null) {
            recordsSetDataService.setErrorRecordsData(userId, errorData, industryCode, examId, categoryId);
        }
    }

    @Operation(summary = "删除单个收藏记录")
    @DeleteMapping("/del-one-collect")
    public void cleanOneCollectRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String qid) {
        var userId = userContext.currentUserId();
        var data = recordsGetDataService.getCollectRecordsResult(userId, industryCode, examId, categoryId);
        NewRecordsDtoItem newRecordsDtoItem = data.get(qid);
        if (null != newRecordsDtoItem) {
            this.pushQuestionCollect(industryCode, examId, categoryId, qid, industryCode, examId, String.valueOf(categoryId), newRecordsDtoItem, newRecordsDtoItem.getCt());
        }
//        var k = data.getData().remove(qid);
//        if (k != null) {
//            recordsSetDataService.setCollectRecordsData(userId, data, industryCode, examId, categoryId);
//        }
    }

    @Operation(summary = "删除试题集合 收藏记录 或者 错题记录")
    @DeleteMapping("/del-chapter-collect")
    public RecordsCountRecordDto cleanChapterCollectRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId,
            @RequestParam String collectId,
            @RequestParam RecordsCollectType recordsType,
            @RequestParam RankType type,
            boolean haveCollect
    ) {
        var userId = userContext.currentUserId();
        var data = recordsGetDataService.getCollectCountRecordsResult(userId, industryCode, examId, categoryId, recordsType, collectId);
        Consumer<String> run = id -> {
            var da = id.equals(collectId) ? data : recordsGetDataService.getCollectCountRecordsResult(userId, industryCode, examId, categoryId, recordsType, id);
            var countData = new RecordsCountRecordDto();
            if (type == RankType.collect) {
                CollectionErrorAndCollectRecordDataDto collectInCollectDataResult = recordsGetDataService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, recordsType, id);
                Set<String> questionId = collectInCollectDataResult.getData().keySet();
                NewQuestionRecordsDto collectRecordsResult = recordsGetDataService.getCollectRecordsResult(userId, industryCode, examId, categoryId);
                questionId.forEach(f -> collectRecordsResult.getData().remove(f));
                recordsSetDataService.setCollectRecordsData(userId, collectRecordsResult, industryCode, examId, categoryId);
            }
            if (type == RankType.collect || type == RankType.all && haveCollect) {
                recordsDelService.delCollectInCollectionData(userId, industryCode, examId, categoryId, recordsType, id);
                if (type == RankType.collect && recordsType.type2CollectType() == QuestionCollectionType.paper) {
                    materialLikeService.delete(
                            Wrappers.<MaterialLikeModel>lambdaUpdate()
                                    .eq(MaterialLikeModel::getCreatedBy, userId)
                                    .eq(MaterialLikeModel::getCategoryId, categoryId)
                                    .eq(MaterialLikeModel::getRecordsType, recordsType)
                                    .eq(MaterialLikeModel::getType, 1)
                                    .eq(MaterialLikeModel::getRelationId, id));
                }
                countData.setCc(-da.getCc());
                da.setCc(0);
            }
            if (type == RankType.error) {
                CollectionErrorAndCollectRecordDataDto errorInCollectDataResult = recordsGetDataService.getErrorInCollectDataResult(userId, industryCode, examId, categoryId, recordsType, id);
                Set<String> questionId = errorInCollectDataResult.getData().keySet();
                NewQuestionRecordsDto errorRecordsResult = recordsGetDataService.getErrorRecordsResult(userId, industryCode, examId, categoryId);
                questionId.forEach(f -> errorRecordsResult.getData().remove(f));
                recordsSetDataService.setErrorRecordsData(userId, errorRecordsResult, industryCode, examId, categoryId);

            }
            if (type == RankType.error || type == RankType.all) {
                if (recordsType.type2CollectType() == QuestionCollectionType.paper) {
                    paperRecordsService.update(null, Wrappers.<PaperRecordsModel>lambdaUpdate().eq(PaperRecordsModel::getId, id).set(PaperRecordsModel::getErrorCount, 0));
                }
                countData.setEc(-da.getEc());
                da.setEc(0);
                recordsDelService.delErrorInCollectData(userId, industryCode, examId, categoryId, recordsType, id);

            }
            if (type == RankType.all) {
                countData.setDc(-da.getDc());
                da.setDc(0);
                recordsDelService.delQuestionRecords(userId, industryCode, examId, categoryId, recordsType, id);
            }

            recordsService.removeCategoryNewCollectRecords(recordsType, id, userId, industryCode, examId, categoryId);
            recordsSetDataService.setCollectCountRecordsData(userId, da, industryCode, examId, categoryId, recordsType, id);
            recordsService.changeCategoryCountData(userId, countData, industryCode, examId, categoryId);
        };
        if (recordsType == RecordsCollectType.chapter) {
            var m = chapterService.getById(collectId);
            var childrenIds = chapterService.allChildrenIds(m);
            var parentIds = chapterService.allParentIds(m);
            Stream.of(1, 2).parallel().forEach(v -> {
                if (v == 1) {
                    for (var id : childrenIds) {
                        run.accept(id);
                    }
                } else {
                    for (var id : parentIds) {
                        var da = recordsGetDataService.getCollectCountRecordsResult(userId, industryCode, examId, categoryId, recordsType, id);
                        if (type == RankType.collect) {
                            da.setCc(da.getCc() - data.getCc());
                        } else if (type == RankType.error) {
                            da.setEc(da.getEc() - data.getEc());
                        } else {
                            da.setDc(da.getDc() - data.getDc());
                            da.setEc(da.getEc() - data.getEc());
                        }
                        recordsSetDataService.setCollectCountRecordsData(userId, da, industryCode, examId, categoryId, recordsType, id);
                    }
                }
            });
        }
        run.accept(collectId);
        return data;
    }

    @Operation(summary = "删除科目收藏记录")
    @DeleteMapping("/del-category-collect")
    @Transactional
    public void cleanCategoryCollectRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId
    ) {
        recordsDelService.delCategoryCollectData(userContext.currentUserId(), industryCode, examId, categoryId);
        materialLikeService.delete(Wrappers.<MaterialLikeModel>lambdaUpdate().eq(MaterialLikeModel::getCreatedBy, userContext.currentUserId())
                .eq(MaterialLikeModel::getType, 1)
                .eq(MaterialLikeModel::getCategoryId, categoryId)
        );
    }

    @Operation(summary = "删除科目错题记录")
    @DeleteMapping("/del-category-error")
    public void cleanCategoryErrorRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId
    ) {
        recordsDelService.delCategoryErrorData(userContext.currentUserId(), industryCode, examId, categoryId);
    }

    @Operation(summary = "删除科目做题数据")
    @DeleteMapping("/clean-category-question-records")
    public void cleanCategoryQuestionRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId) {
        recordsDelService.delCategoryQuestionRecords(userContext.currentUserId(), industryCode, examId, categoryId);
    }


    @Operation(summary = "删除科目所有记录")
    @DeleteMapping("/clean-category")
    public void cleanCategoryRecords(
            @RequestParam String industryCode,
            @RequestParam String examId,
            @RequestParam Long categoryId) {
        String userId = userContext.currentUserId();
        Stream.of(1, 2).parallel().forEach(v -> {
            if (v == 1) {
                // 删除oss记录
                recordsDelService.delCategoryData(userId, industryCode, examId, categoryId);
            } else if (v == 2) {
                // 删除试卷记录
                paperRecordsService.delUserAllRecords(userId, categoryId);
            } else if (v == 3) {
                // 删除试卷收藏
                materialLikeService.delUserAllPaperCollect(userId, categoryId);
            }
        });
    }


    private QuestionCollectVo getCollectData(
            String userId,
            String industryCode,
            String examId,
            Long categoryId,
            QuestionCollectionType collectionType,
            String collectId,
            Boolean leaf,
            boolean error,
            Boolean groupByCollect,
            boolean view,
            String industryName,
            String examName,
            String categoryName
    ) {
        @Getter
        @Setter
        class V {
            private String cid;
            private String cn;
            private RecordsCollectType ct;
            private List<QuestionCollectionBlockItem> content;
        }

        List<V> data = new LinkedList<>();
        all:
        if (collectionType == QuestionCollectionType.chapter) {
            if (leaf && StrUtil.isNotEmpty(collectId)) {
                var m = chapterService.getById(collectId);
                var i = new V();
                i.setCid(collectId);
                i.setCt(RecordsCollectType.chapter);
                i.setCn(m.getName());
                i.setContent(m.getContent());
                data.add(i);
            } else {
                var key = error ? ossFileService.getErrorInCollectDataKey(userId, industryCode, examId, categoryId, RecordsCollectType.chapter, "test") :
                        ossFileService.getCollectInCollectionDataKey(userId, industryCode, examId, categoryId, RecordsCollectType.chapter, "test");
                var keys = ossFileService.getPrefixKeys(ossFileService.getOssPlugins(userId).getFilePath(key.replace(String.format("_%s_%s.json", RecordsCollectType.chapter, "test"), "")), userId);
                keys = keys.stream().filter(v -> v.contains("_chapter_")).map(v -> {
                    var s = v.split("_");
                    return s[s.length - 1].replace(".json", "");
                }).toList();
                if (keys.isEmpty()) break all;
                if (StrUtil.isEmpty(collectId)) {
                    chapterService.selectList(chapterService.wrappers().select(ChapterModel::getName, BaseEntity::getId, ChapterModel::getContentMap)
                                    .in(BaseEntity::getId, keys))
                            .forEach(v -> {
                                var i = new V();
                                i.setCid(v.getId());
                                i.setCt(RecordsCollectType.chapter);
                                i.setCn(v.getName());
                                i.setContent(v.getContent());
                                data.add(i);
                            });
                } else {
                    chapterService.allChildren(collectId, -1, chapterService.wrappers().select(ChapterModel::getName, BaseEntity::getId, ChapterModel::getContentMap).in(BaseEntity::getId, keys))
                            .forEach(v -> {
                                var i = new V();
                                i.setCid(v.getId());
                                i.setCt(RecordsCollectType.chapter);
                                i.setCn(v.getName());
                                i.setContent(v.getContent());
                                data.add(i);
                            });
                }
            }
        } else if (collectionType == QuestionCollectionType.paper) {
            var i = new V();
            var m = paperRecordsService.getById(collectId);
            if (m != null) {
                i.setCid(m.getId());
                i.setCt(RecordsCollectType.paperRecordType2Type(m.getType()));
                i.setCn(m.getName());
                i.setContent(m.getContent());
            }
            if (!error && m == null) {
                var p = paperService.getById(collectId);
                if (p == null) return new QuestionCollectVo();
                i.setCid(p.getId());
                i.setCt(RecordsCollectType.paperRecordPaperLibrary);
                i.setCn(p.getName());
                i.setContent(p.getContent());
            }
            data.add(i);
        } else if (collectionType == QuestionCollectionType.everyDayPractice) {
            var m = everyDayPracticeService.getById(collectId);
            var i = new V();
            i.setCid(m.getId());
            i.setCt(RecordsCollectType.everyday);
            i.setCn(m.getName());
            i.setContent(m.getContent());
            data.add(i);
        } else {
            var rt = RecordsCollectType.collectType2Type(collectionType);
            if (rt == null) return new QuestionCollectVo();
            var i = new V();
            i.setCid(collectId);
            i.setCt(rt);
            if (rt == RecordsCollectType.rankError || rt == RecordsCollectType.rankCollect || rt == RecordsCollectType.hotQue) {
                var time = collectId.replaceAll("\\D", "");
                var t = LocalDate.now();
                if (StrUtil.isNotBlank(time)) {
                    t = LocalDate.parse(time, TimeUtil.DATE_TIME_FORMATTER_DAY_YMD);
                }
                i.setCn(otherCollectName(industryName, examName, categoryName, t, rt));
            }
            i.setContent(new LinkedList<>());
            data.add(i);
        }
        if (data.isEmpty()) return new QuestionCollectVo();
        var allData = new NewQuestionRecordsDto();
        var collectData = new CollectionErrorAndCollectRecordDataDto();
        data.parallelStream().forEach(v -> {
            var collectRecord = recordsGetDataService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, v.getCt(), v.getCid());
            var record = error ? recordsGetDataService.getQuestionRecordsResult(userId, industryCode, examId, categoryId, v.getCt(), v.getCid()) : collectRecord;
            collectData.getData().putAll(collectRecord.getData());

            record.getData().keySet().forEach(qid -> {
                if (qid.equals(QuestionRecordsDto.LAST_ID)) {
                    return;
                }
                var itemObj = record.get(qid);
                var i = new NewRecordsDtoItem();
                if (itemObj instanceof QuestionRecordsDtoItem iq) {
                    if (iq.isRt()) return;
                    i.setAs(iq.getAs());
                    i.setT(iq.getT());
                } else {
                    i.setT((Long) itemObj);
                }
                i.setCid(v.getCid());
                i.setCt(v.getCt());
                i.setCn(v.getCn());
                if (QuestionUtil.questionIdIsCombinationChild(qid)) {
                    var qm = questionService.selectOne(questionService.wrappers()
                            .select(QuestionModel::getSourceId, QuestionModel::getFirstType, QuestionModel::getTypeId)
                            .eq(BSEntity::getId, qid)
                    );
                    if (qm == null) return;
                    allData.put(qm.getSourceId(), i);
                    for (var i1 : v.getContent()) {
                        if (i1.getQs().stream().anyMatch(v1 -> v1.getId().equals(qm.getSourceId()))) {
                            i.setTr(i1.getTyName());
                        }
                    }
                    if (i.getTr() == null) {
                        i.setTr(questionTypeService.queryName(qm.getTypeId(), qm.getFirstType()));
                    }
                } else {
                    for (var i1 : v.getContent()) {
                        if (i1.getQs().stream().anyMatch(v1 -> v1.getId().equals(qid))) {
                            i.setTr(i1.getTyName());
                        }
                    }
                }
                allData.put(qid, i);
            });
        });
        List<String> noTr = new LinkedList<>();
        allData.getData().forEach((k, v) -> {
            if (v instanceof NewRecordsDtoItem vi && vi.getTr() == null) {
                noTr.add(k);
            }
        });
        if (!noTr.isEmpty()) {
            var qtMap = questionService.selectList(questionService.wrappers()
                    .select(BSEntity::getId, QuestionModel::getFirstType, QuestionModel::getTypeId)
                    .in(BSEntity::getId, noTr)
            ).stream().collect(Collectors.toMap(BSEntity::getId, v -> questionTypeService.queryName(v.getTypeId(), v.getFirstType())));
            allData.getData().forEach((k, v) -> {
                if (v instanceof NewRecordsDtoItem vi && vi.getTr() == null) {
                    vi.setTr(qtMap.get(k));
                }
            });
        }
        return errorOrCollect2CollectionData(allData, collectData, userId, industryCode, examId, categoryId, groupByCollect, error, view);
    }


    private <T extends OssMapDto<NewRecordsDtoItem, T>> QuestionCollectVo errorOrCollect2CollectionData(
            OssMapDto<NewRecordsDtoItem, T> data,
            CollectionErrorAndCollectRecordDataDto collectRecordDataDto,
            String userId,
            String industryCode,
            String examId,
            Long categoryId,
            Boolean groupByCollect,
            boolean error,
            boolean view
    ) {
        var model = new ChapterModel();
        model.setName("错题集合");
        class V {
            V(String qid, String type, String cn) {
                this.qid = qid;
                this.type = type;
                this.cn = cn;
            }

            final String qid;
            final String type;
            final String cn;
        }
        List<String> qids = new LinkedList<>();
        Map<String, String> qt = new HashMap<>();
        var recordsDto = new QuestionRecordsDto();
        model.setContent(data.getData().keySet().stream()
                // 将错题记录转换为 题型 id
                .map(key -> {
                    var k = data.get(key);
                    if (!error) {
                        collectRecordDataDto.put(key, k.getT());
                    }
                    if (!QuestionUtil.questionIdIsCombinationChild(key)) {
                        qids.add(key);
                    }
                    qt.put(key, k.getTr());
                    if (view && error) {
                        var i = new QuestionRecordsDtoItem();
                        i.setAs(k.getAs());
                        i.setRt(false);
                        i.setT(k.getT());
                        recordsDto.put(key, i);
                    }
                    return new V(key, k.getTr(), k.getCn());
                })
                // 将同类题型集合
                .collect(Collectors.groupingBy(v -> groupByCollect ? v.cn : v.type, Collectors.mapping(v -> v.qid, Collectors.toSet())))
                .entrySet()
                .stream()
                // 将集合转换为List<QuestionCollectionBlockItem>
                .map(e -> {
                    var blockItem = new QuestionCollectionBlockItem();
                    blockItem.setTyName(e.getKey());
                    blockItem.setQs(e.getValue().stream().map(qid -> {
                        var i = new QuestionCollectionBlockItem.QuestionCollectionItem();
                        i.setId(qid);
                        return i;
                    }).toList());
                    return blockItem;
                })
                .toList()
        );
        var questionList = questionService.clientQuestionCollection(qids, industryCode, examId, categoryId, false);
        for (var q : questionList) {
            if (QuestionUtil.questionIdIsCombinationParent(q.getId())) {
                q.getChildren().parallelStream().forEach(f -> {
                    f.setType(f.getFirstType().getDesc());
                });
            }
            q.setType(qt.get(q.getId()));
        }
        var result = coreQuestionCollectionService.clientCollectionData(model, questionList, industryCode, examId, categoryId);
        result.setId(userId);
        coreQuestionCollectionService.collectVoForEach(result, vo -> {
            var k = data.get(vo.getId());
            if (k != null) {
                vo.setCid(k.getCid());
                vo.setCn(k.getCn());
            }
        });
        return coreQuestionCollectionService.safeProcess(recordsDto, collectRecordDataDto, result, userId, userId, false, view);
    }

    private String otherCollectName(String industryName, String examName, String categoryName, LocalDate time, RecordsCollectType rt) {
        var t = time == null ? "" : TimeUtil.DATE_TIME_FORMATTER_DAY_YMD.format(time);
        if (rt == RecordsCollectType.rankError) {
            return String.format("%s-%s高频错题%s", examName, categoryName, "");
        } else if (rt == RecordsCollectType.hotQue) {
            return String.format("%s-%s热门试题%s", examName, categoryName, "");
        } else if (rt == RecordsCollectType.rankCollect) {
            return String.format("%s-%s热门收藏%s", examName, categoryName, "");
        }
        throw new RuntimeException();
    }

    //获取收藏试卷整个试题
    private QuestionCollectVo getPaperCollectInfo(MaterialLikeModel model, String userId, String industryCode, String examId, Long categoryId) {
        QuestionCollection<?, ?> questionCollection;
        if (model.getRecordsType().equals(RecordsCollectType.paperRecordPaperLibrary)) {
            questionCollection = paperService.getById(model.getRelationId());
        } else {
            questionCollection = paperRecordsService.getById(model.getRelationId());
        }
        //获取收藏
        CollectionErrorAndCollectRecordDataDto collectInCollectDataResult = recordsGetDataService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, model.getRecordsType(), model.getRelationId());

        var questionCollectVo = coreQuestionCollectionService.clientCollectionData(questionCollection, industryCode, examId, categoryId);
        coreQuestionCollectionService.safeProcess(new QuestionRecordsDto(), collectInCollectDataResult, questionCollectVo, userId, userId, false);
        questionCollectVo.setRecordsType(model.getRecordsType());
        questionCollectVo.setId(userId);
        coreQuestionCollectionService.collectVoForEach(questionCollectVo, q -> {
            q.setCid((String) questionCollection.getId());
            q.setCn(questionCollection.getName());
            if (StrUtil.isBlank(q.getType())) {
                q.setType(q.getFirstType().getDesc());
            }
        });
        return questionCollectVo;
    }
    @GetMapping("/getFiles")
    public List<String> getFiles(@RequestParam(defaultValue = "") String path){


        OSS client = recordsOssPlugins.createClient();
        if(path.isBlank()){
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(recordsOssConfig.getBucket());
            listObjectsRequest.setPrefix("");
//            listObjectsRequest.setMarker();
            listObjectsRequest.setDelimiter("/");





            // 列出文件。
            ObjectListing listing = client.listObjects(listObjectsRequest);
            List<String> commonPrefixes = listing.getCommonPrefixes();

            for (String dir : commonPrefixes) {
                System.out.println(" - " + dir);
            }


            List<OSSObjectSummary> sums = listing.getObjectSummaries();
            for (OSSObjectSummary s : sums) {
                System.out.println(" - " + s.getKey() + "  (size = " + s.getSize() + ")");
            }
        }else{//   exam/records
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(recordsOssConfig.getBucket());
            listObjectsRequest.setPrefix(path);
            listObjectsRequest.setDelimiter("/");

            // 列出文件。
            ObjectListing listing = client.listObjects(listObjectsRequest);
            List<String> commonPrefixes = listing.getCommonPrefixes();

            for (String dir : commonPrefixes) {
                System.out.println(" - " + dir);
            }



            List<OSSObjectSummary> sums = listing.getObjectSummaries();
            for (OSSObjectSummary s : sums) {
                System.out.println(" - " + s.getKey() + "  (size = " + s.getSize() + ")");
            }
        }
        return null;
    }
}
